/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace OsHelper.UsbNet.Descriptors
{
	/// <summary>
	/// A structure representing the standard USB interface descriptor. This
	/// descriptor is documented in section 9.6.5 of the USB 2.0 specification.
	/// </summary>
	public class InterfaceDescriptor
	{
		/// <summary>
		/// Total size of this structure in bytes.
		/// </summary>
		public static int Size {
			get { return Marshal.SizeOf (typeof(InterfaceDescriptorHeader)); }
		}

		[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 9)]
		private class InterfaceDescriptorHeader
		{
			
			[FieldOffsetAttribute(0)]
			private Descriptor BaseDesc;
			
			/// <summary>
			/// Number of this interface
			/// </summary>
			[FieldOffsetAttribute(2)]
			public byte Number;

			/// <summary>
			/// Value used to select this alternate setting for this interface
			/// </summary>
			[FieldOffsetAttribute(3)]
			public byte AlternateSetting;

			/// <summary>
			/// Number of endpoints used by this interface (excluding the control endpoint).
			/// </summary>
			[FieldOffsetAttribute(4)]
			public byte NumberEndpoints;

			/// <summary>
			/// USB-IF class code for this interface.
			/// See ClassCodeType.
			/// </summary>
			[FieldOffsetAttribute(5)]
			public ClassCodeType Class;

			/// <summary>
			/// USB-IF subclass code for this interface,
			/// qualified by the InterfaceClass value
			/// </summary>
			[FieldOffsetAttribute(6)]
			public byte SubClass;

			/// <summary>
			/// USB-IF protocol code for this interface,
			/// qualified by the InterfaceClass and InterfaceSubClass values
			/// </summary>
			[FieldOffsetAttribute(7)]
			public byte Protocol;

			/// <summary>
			/// Index of string descriptor describing this interface
			/// </summary>
			[FieldOffsetAttribute(8)]
			public byte StringIndex;
			
			public byte Length {
				get { return this.BaseDesc.Length; }
				set { this.BaseDesc.Length = value; }
			}
			
			public UsbNet.Descriptors.DescriptorType Type {
				get { return this.BaseDesc.Type; }
				set { this.BaseDesc.Type = value; }
			}
		}

		private InterfaceDescriptorHeader InterafaceDesc;
		
		public InterfaceDescriptor ()
		{
			this.InterafaceDesc = new InterfaceDescriptor.InterfaceDescriptorHeader();
			this.EndpointList = new List<EndpointDescriptor>();
			
			this.Length = (byte)InterfaceDescriptor.Size;
			this.Type = new DescriptorType (StandardDescriptorSubType.Interface);
		}

		public byte[] ToBytes()
		{
			throw new NotImplementedException();
		}
		
		public static InterfaceDescriptor FromBytes (byte[] Data)
		{
			if (Data == null)
				throw new ArgumentNullException ("Data");
			
			if (Data.Length < InterfaceDescriptor.Size)
				throw new ArgumentException (string.Format("Data.Length: {0}, DeviceDescriptor.Size: {1}", Data.Length, DeviceDescriptor.Size));
			
			byte[] InterfDescData = new byte[InterfaceDescriptor.Size];
			Array.Copy(Data, 0, InterfDescData, 0, InterfaceDescriptor.Size);
			
			InterfaceDescriptor IntDesc = new InterfaceDescriptor();
			IntDesc.InterafaceDesc = (InterfaceDescriptorHeader)OsHelper.Serialyser.RawDeserialize(InterfDescData, typeof(InterfaceDescriptorHeader));
			
			DescriptorType EndpointType = new DescriptorType (StandardDescriptorSubType.Endpoint);
			
			int Pos = 0;
			int FoundEndpoint = 0;
			
			while (Pos + Descriptor.Size <= Data.Length && FoundEndpoint < IntDesc.InterafaceDesc.NumberEndpoints) {
				byte[] DescData = new byte[Descriptor.Size];
				Array.Copy(Data, Pos, DescData, 0, Descriptor.Size);
				Descriptor Desc = Descriptor.FromBytes (DescData);
				
				if (Desc.Length < Descriptor.Size || Desc.Length > Data.Length - Pos)
					throw new InvalidDescriptorException ("The device has errors in its Interface descriptor.");
				
				if (Desc.Type == EndpointType && Pos + EndpointDescriptor.Size <= Data.Length) {
					FoundEndpoint++;
					
					byte[] EndpointData = new byte[EndpointDescriptor.Size];
					Array.Copy(Data, Pos, EndpointData, 0, EndpointDescriptor.Size);
					
					IntDesc.EndpointList.Add(EndpointDescriptor.FromBytes(EndpointData));
				}
				
				Pos += Desc.Length;
			}
			
			return IntDesc;
		}
		
		public byte Length {
			get { return this.InterafaceDesc.Length; }
			private set { this.InterafaceDesc.Length = value; }
		}
		
		public UsbNet.Descriptors.DescriptorType Type {
			get { return this.InterafaceDesc.Type; }
			private set { this.InterafaceDesc.Type = value; }
		}
		
		/// <summary>
		/// Number of this interface
		/// </summary>
		public byte Number {
			get { return this.InterafaceDesc.Number; }
			set { this.InterafaceDesc.Number = value; }
		}

		/// <summary>
		/// Value used to select this alternate setting for this interface
		/// </summary>
		public byte AlternateSetting {
			get { return this.InterafaceDesc.AlternateSetting; }
			set { this.InterafaceDesc.AlternateSetting = value; }
		}
		
		/// <summary>
		/// Number of endpoints used by this interface (excluding the control endpoint).
		/// </summary>
		public byte NumberEndpoints {
			get { return this.InterafaceDesc.NumberEndpoints; }
			set { this.InterafaceDesc.NumberEndpoints = value; }
		}

		/// <summary>
		/// USB-IF class code for this interface.
		/// See ClassCodeType.
		/// </summary>
		public ClassCodeType Class {
			get { return this.InterafaceDesc.Class; }
			set { this.InterafaceDesc.Class = value; }
		}

		/// <summary>
		/// USB-IF subclass code for this interface,
		/// qualified by the InterfaceClass value
		/// </summary>
		public byte SubClass {
			get { return this.InterafaceDesc.SubClass; }
			set { this.InterafaceDesc.SubClass = value; }
		}

		/// <summary>
		/// USB-IF protocol code for this interface,
		/// qualified by the InterfaceClass and InterfaceSubClass values
		/// </summary>
		public byte Protocol {
			get { return this.InterafaceDesc.Protocol; }
			set { this.InterafaceDesc.Protocol = value; }
		}

		/// <summary>
		/// Index of string descriptor describing this interface
		/// </summary>
		public byte StringIndex {
			get { return this.InterafaceDesc.StringIndex; }
			set { this.InterafaceDesc.StringIndex = value; }
		}

		/// <summary>
		/// Array of endpoint descriptors.
		/// This length of this array is determined by the NumEndpoints field.
		/// </summary>
		public List<EndpointDescriptor> EndpointList { get; set; }
		
	}
}
